/*=========================================================================
| KCardDAV
|--------------------------------------------------------------------------
| (c) 2010  Kumaran Santhanam
|
| This project is released under the GNU General Public License.
| Please see the file COPYING for more details.
|--------------------------------------------------------------------------
| General job class.
 ========================================================================*/

/*=========================================================================
| INCLUDES
 ========================================================================*/

#include "job.h"
#include "resource.h"

#include <kdebug.h>
#include <klocalizedstring.h>

#include <QtCore/QString>
#include <QtCore/QUrl>
#include <QtCore/QByteArray>

#include <neon/ne_session.h>
#include <neon/ne_request.h>
#include <neon/ne_auth.h>

#define log(s)      kDebug() << s;

/*=========================================================================
| NAMESPACE
 ========================================================================*/

using namespace KABC::carddav;

/*=========================================================================
| Impl
 ========================================================================*/

struct Job::Impl {
    Job* job;
    QUrl url;
    ne_session* session;
    ne_request* request;
    int request_count;

    struct {
        int code;
        QString info;
    } error;

    static int accept_response(void *userdata, ne_request *req, const ne_status *st);
    static int server_auth(void *userdata, const char *realm, int attempts, char *username, char *password);
    static int block_reader(void *userdata, const char *buf, size_t len);
    static int ssl_verify(void *userdata, int failures, const ne_ssl_certificate *cert);

    Impl(Job* aJob, const QUrl& Url);
    ~Impl();

    void setError(int code, const QString& info);

    void createSession();
    void createRequest();
    void destroyRequest();
    void destroySession();

    void dispatchRequest(Job* job);

    void run(Job* job);
};

int Job::Impl::accept_response(void *userdata, ne_request *req, const ne_status *st) {
    log("acceptance callback called");
    Impl* impl = static_cast<Impl*>(userdata);
    return impl->job->acceptResponse(req, st);
}

int Job::Impl::server_auth(void *userdata, const char */*realm*/, int attempts, char *username, char *password) {
    log("server auth called");
    Impl* impl = static_cast<Impl*>(userdata);
    int ret = -1;
    if (impl) {
        log("username: " << impl->url.userName() << ", password: " << impl->url.password());
        strncpy(username, impl->url.userName().toStdString().c_str(), NE_ABUFSIZ);
        strncpy(password, impl->url.password().toStdString().c_str(), NE_ABUFSIZ);
        ret = attempts;
    }
    return ret;
}

int Job::Impl::block_reader(void *userdata, const char *buf, size_t len) {
    Impl* impl = static_cast<Impl*>(userdata);
    if (impl) {
        impl->job->blockRead(buf, len);
    }
    return 0;
}

int Job::Impl::ssl_verify(void */*userdata*/, int /*failures*/, const ne_ssl_certificate */*cert*/) {
    // TODO: this effectively disables certificate checking. Are we OK with it?
    return 0;
}

Job::Impl::Impl(Job* aJob, const QUrl& Url)
    : job(aJob)
    , url(Url)
    , session(0)
    , request(0)
    , request_count(1)
{
    setError(0, "");
}

Job::Impl::~Impl() {
    destroyRequest();
    destroySession();
}

void Job::Impl::setError(int code, const QString& info) {
    error.code = code;
    error.info = info;
}

void Job::Impl::createSession() {
    destroySession();
    const int k_default_port = "https" == url.scheme().toLower() ? 443 : 80;
    log("creating session. Scheme: " << url.scheme() << ", host: " << url.host() << ", port: " << url.port(k_default_port));
    session = ne_session_create(url.scheme().toStdString().c_str(),
                                url.host().toStdString().c_str(),
                                url.port(k_default_port)
                                );
    ne_set_server_auth(session, server_auth, this);
    ne_ssl_set_verify(session, ssl_verify, this);
}

void Job::Impl::createRequest() {
    destroyRequest();
    request = job->createRequest(session);
    ne_add_response_body_reader(request, accept_response, block_reader, this);
}

void Job::Impl::destroyRequest() {
    if (request) {
        log("destroying request");
        ne_request_destroy(request);
        request = 0;
    }
}

void Job::Impl::destroySession() {
    if (session) {
        log("destroying session");
        ne_session_destroy(session);
        session = 0;
    }
}

void Job::Impl::dispatchRequest(Job* job) {
    int r = ne_request_dispatch(request);
    if(NE_OK == r) {
        // processing the headers
        void* cursor = 0;
        const char* name = 0;
        const char* value = 0;
        while ((cursor = ne_response_header_iterate(request, cursor, &name, &value))) {
            job->processHeader(name, value);
        }
    } else {
        // error
        job->setError(r, ne_get_error(session));
        log("error while dispatching request: " << job->errorInfo());
    }
    job->requestFinished();
}

void Job::Impl::run(Job* job) {
    log("calling createSession");
    createSession();
    while (job->requestAgain()) {
        log("calling createRequest");
        createRequest();
        log("calling dispatchRequest");
        dispatchRequest(job);
        log("calling destroyRequest");
        destroyRequest();
    }
    log("calling destroySession");
    destroySession();
}

/*=========================================================================
| CONSTRUCTOR AND DESTRUCTOR
 ========================================================================*/

Job::Job(const QUrl& url)
    : m_i(new Impl(this, url))
{
}

Job::~Job() {
    delete m_i;
}


/*=========================================================================
| PUBLIC METHODS
 ========================================================================*/

void Job::syncStart() {
    run();
}

int Job::error() const {
    return m_i->error.code;
}

QString Job::errorInfo() const {
    return m_i->error.info;
}

void Job::finish(ResourceCardDav* customer) {
    if (!error()) {
        jobFinished(customer);
    }
}

/*=========================================================================
| PROTECTED AND PRIVATE METHODS
 ========================================================================*/

void Job::run() {
    log("running job");
    setError(0, "");
/*    m_i->createSession();
    runJob();
    m_i->destroySession();*/
    m_i->run(this);
}

void Job::setError(int code, const QString& info) {
    m_i->setError(code, info);
}

const QUrl& Job::url() const {
    return m_i->url;
}

void Job::processHeader(const char* /*name*/, const char* /*value*/) {
    // to be implemented in subclasses
}

bool Job::requestAgain() {
    return 0 < m_i->request_count--;
}

void Job::requestFinished() {
    // to be implemented in subclasses
}

bool Job::acceptResponse(ne_request */*req*/, const ne_status *st) {
    // TODO: add error-handling here
    log("response status, code: " << st->code << ", class: " << st->klass << ", reason: " <<st->reason_phrase);
    return (2 == st->klass);
}

void Job::jobFinished(ResourceCardDav* /*customer*/) {
    // to be implemented in subclasses
}

// EOF ========================================================================
